package com.wtf.dsp.admin.core.parser;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.pagination.DialectFactory;
import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel;
import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect;
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
import com.wtf.dsp.admin.enums.CommonErrorEnum;
import com.wtf.dsp.admin.exception.WtfDspException;
import com.wtf.dsp.admin.model.DbSql;
import com.wtf.dsp.core.constant.Constant;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Optional;

/**
 * description: SQL解析器
 * date: 2022/6/18 10:59
 * @author linhuanyou
 * @email 496558736@qq.com
 * @version 1.0
 */
public class SqlParser {


    private static String $key = "#{}";

    private static String prefix = "<" + $key + ">";

    private static String suffix = "</" + $key + ">";

    private static final int MAX_LIMIT = 100;

    public static DbSql obtainSql(Map<String,String> params, String sql, DbType dbType) throws Exception {
        if (CollUtil.isEmpty(params)) {
            throw new WtfDspException(CommonErrorEnum.ERROR_VALID);
        }
        DbSql dbSql = new DbSql();
        boolean pageFlag = false;
        Integer page = null;
        Integer limit = null;
        if (CollUtil.isNotEmpty(params) && (params.containsKey(Constant.PAGE) || params.containsKey(Constant.LIMIT))) {
            pageFlag = true;
            page = StringUtils.hasLength(params.get(Constant.PAGE)) ? Integer.parseInt(params.get(Constant.PAGE)) : 1;
            limit = StringUtils.hasLength(params.get(Constant.LIMIT)) ? Integer.parseInt(params.get(Constant.LIMIT)) <= MAX_LIMIT ? Integer.parseInt(params.get(Constant.LIMIT)) : MAX_LIMIT : MAX_LIMIT;
            params.remove(Constant.PAGE);
            params.remove(Constant.LIMIT);
        }

        for (Map.Entry<String, String> entry : params.entrySet()){
            String key = entry.getKey();
            String value = entry.getValue();
            String prefixKey = prefix.replace($key, key);
            String suffixKey = suffix.replace($key, key);
            int prefixKeyIndex = sql.indexOf(prefixKey);
            int suffixKeyIndex = sql.indexOf(suffixKey);
            boolean dynamicParamFlag = prefixKeyIndex > 0 && suffixKeyIndex > 0;
            if (dynamicParamFlag) {
                if (StringUtils.isEmpty(value)) {
                    StringBuilder excSqlSb = new StringBuilder(sql);
                    sql = excSqlSb.delete(prefixKeyIndex, suffixKeyIndex + suffixKey.length()).toString();
                } else {
                    sql = sql.replace(prefixKey, "").replace(suffixKey, "").replace(key, value);
                }
            } else {
                sql = sql.replace(key, value);
            }
        }

        if(pageFlag){
            boolean selectSql = sql.startsWith("select") || sql.startsWith("SELECT");
            if (!selectSql) {
                throw new WtfDspException(CommonErrorEnum.ERROR_PAGE_SELECT_SQL);
            }
            String countSql = SqlParserUtils.getOptimizeCountSql(true, null, sql).getSql();
            dbSql.setCountSql(countSql);
            sql = dialectSql(sql, page, limit,dbType);
        }
        dbSql.setExcSql(sql);
        return dbSql;
    }

    public static String dialectSql(String excSql, long page, long limit, DbType dbType) throws Exception {
        if (dbType == null) {
            throw new WtfDspException(CommonErrorEnum.ERROR_DBTYPE);
        }
        IDialect dialect = (IDialect) Optional.ofNullable(null).orElseGet(() -> {
            return DialectFactory.getDialect(dbType);
        });
        long offset = page > 0L ? (page - 1L) * limit : 0L;
        DialectModel model = dialect.buildPaginationSql(excSql, offset, limit);
        Field firstParam = model.getClass().getDeclaredField("firstParam");
        firstParam.setAccessible(true);
        String firstParamValue = firstParam.get(model).toString();
        Field secondParam = model.getClass().getDeclaredField("secondParam");
        secondParam.setAccessible(true);
        String secondParamValue = secondParam.get(model).toString();
        StringBuilder builder = new StringBuilder(model.getDialectSql());
        int first = builder.indexOf("?");
        builder.replace(first, first + 1, firstParamValue);
        int second = builder.indexOf("?");
        builder.replace(second, second + 1, secondParamValue);
        return builder.toString();
    }
}
